/** ****************************************************************************
@defgroup Wheel Wheel
@ingroup BSP_External
@brief Wheel 驱动
@addtogroup Wheel
@{
*******************************************************************************/

#include "wheel.h"
#include "nonos.h"

#define LOGTAG "WHEEL"

// forwards
static OSTaskDef(checkButtonStateTask);

void wheel_init(driver_wheel_t* dev, uint8_t totalGears, FUNC_u8_void funcIsDown, FUNC_GPIO_IsInOn funcPinAIsOn, FUNC_GPIO_IsInOn funcPinBIsOn) {
	if(!dev) { return; }
	dev->totalGears = totalGears;
	dev->listen = false;
	dev->taskIdWheel = NULL;
	dev->funcPinAIsOn = funcPinAIsOn;
	dev->funcPinBIsOn = funcPinBIsOn;
	dev->funcIsDown   = funcIsDown;
}

void wheel_setupCallbacks(
	driver_wheel_t* dev, 
	FUNC_void_voidp   onDown,       void* cookieDown,
	FUNC_void_voidp   onUp,         void* cookieUp,
	FUNC_void_voidp   onClick,      void* cookieClick,
	FUNC_void_voidp   onLongPress1, void* cookieLongPress1,
	FUNC_void_voidp   onLongPress2, void* cookieLongPress2,
    FUNC_onWheelSteps onSteps,      void* cookieSteps,
    FUNC_onWheelStep  onStep,       void* cookieStep )
{
	if(!dev) { return; }
	dev->onDown       = onDown;         dev->cookieDown       = cookieDown;
	dev->onUp         = onUp;           dev->cookieUp         = cookieUp;
	dev->onClick      = onClick;        dev->cookieClick      = cookieClick;
	dev->onLongPress1 = onLongPress1;   dev->cookieLongPress1 = cookieLongPress1;
	dev->onLongPress2 = onLongPress2;   dev->cookieLongPress2 = cookieLongPress2;
	dev->onSteps      = onSteps;        dev->cookieSteps      = cookieSteps;
	dev->onStep       = onStep;         dev->cookieStep       = cookieStep;
}

void wheel_listen(driver_wheel_t* dev, bool enable) { 
	if(!dev) { return; }
	
	// 使能按钮监听
	if(enable && dev->funcIsDown) {
		// 初始化按钮状态
		dev->btnState = BUTTON_UP;
		dev->btnDownTime = 0;
		dev->onDownTriggered = false;
		dev->onLongPress1Triggered = false;
		dev->onLongPress2Triggered = false;
		// 创建轮询任务
		if(!dev->taskIdBtn) { dev->taskIdBtn = osTaskRunRoutine(checkButtonStateTask, sizeof(void*), &dev); }
	} else if(!enable && dev->taskIdBtn) {
		// 如果之前已经创建了轮询任务，但要停止监听，就必须停止任务
		osTaskDestroy(&dev->taskIdBtn);
	}
	
	// 初始化旋钮状态
	dev->downOnSpin = false;
	dev->spinState = dev->funcPinBIsOn();
	dev->steps = 0;
	dev->spinBeginTime = 0;
	// 使能旋钮监听
	dev->listen = dev->funcPinAIsOn && dev->funcPinBIsOn && enable;
}

/// @brief 按钮状态轮询处理循环
/// @brief 按钮状态轮询处理循环
static OSTaskDef(checkButtonStateTask) {
	if(!cookieLen || !cookieData) { return; }
	driver_wheel_t *dev = (driver_wheel_t*)getPointer(cookieData);
	if(!dev || !dev->funcIsDown) { return; }

	// case0: 如果旋钮旋转未结束(dev->spinBeginTime != 0)，
	// 或曾经在旋钮旋转状态下按下过按钮(dev->downOnSpin == true)，
	// 则不触发任何按钮事件
	if(dev->spinBeginTime || dev->downOnSpin) { return; }
	
    // 获取按键状态
    uint8_t newState = dev->funcIsDown();

    // case1: 无按键按下，不管
    if((newState == BUTTON_UP) && (dev->btnState == BUTTON_UP)) {
        dev->btnDownTime = 0;
        return;
    }

    // case2: 按键刚按下，记住按下时间
    if((newState == BUTTON_DOWN) && (dev->btnState == BUTTON_UP)) {
        dev->btnState = BUTTON_DOWN;
        dev->btnDownTime = osGetMs();
		dev->onDownTriggered = false;
		dev->onLongPress1Triggered = false;
		dev->onLongPress2Triggered = false;
        return;
    }
	
    // 计算按下了多长时间
    uint32_t keepTime = osElapsedMs(dev->btnDownTime);

    // case3: 按钮松开
	if((newState == BUTTON_UP) && (dev->btnState == BUTTON_DOWN)) {
        dev->btnState = BUTTON_UP;

        if(keepTime < BUTTON_JITTER_TIME) {
            // 按键抖动，不管
            return;
        } else if(keepTime < BUTTON_LONGPRESS_TIME1) {
            // 触发click事件
            if(dev->onClick) { dev->onClick(dev->cookieClick); }
        }

        // 触发 Up 事件
        if(dev->onUp) { dev->onUp(dev->cookieUp); }
        return;
    }

    // case4: 按键持续按下
    if((newState == BUTTON_DOWN) && (dev->btnState == BUTTON_DOWN)) {
        if(keepTime >= BUTTON_LONGPRESS_TIME2) {
            // 触发 LongPress2 事件
            if(dev->onLongPress2 && !dev->onLongPress2Triggered) {
				dev->onLongPress2Triggered = true;
				dev->onLongPress2(dev->cookieLongPress2);
			}
        } else if(keepTime >= BUTTON_LONGPRESS_TIME1) {
            // 触发 LongPress1 事件
            if(dev->onLongPress1 && !dev->onLongPress1Triggered) {
				dev->onLongPress1Triggered = true;
				dev->onLongPress1(dev->cookieLongPress1);
			}
        } else if(keepTime >= BUTTON_JITTER_TIME) {
            // 触发 Down 事件
            if(dev->onDown && !dev->onDownTriggered) {
				dev->onDownTriggered = true;
				dev->onDown(dev->cookieDown);
			}
        }
        return;
    }
}

static OSTaskDef(checkSpinStateTask) {
	if(!cookieLen || !cookieData) { return; }
	driver_wheel_t* dev = (driver_wheel_t*)getPointer(cookieData);
	if(!dev) { return; }

	if(dev->steps && dev->onSteps) {
		// 旋转停止了，计算所花的时间
		int interval = osElapsedMs(dev->spinBeginTime) - WHEEL_LAZY_INTERVAL;
		// 触发事件回调函数
		dev->onSteps(dev->downOnSpin, dev->steps, dev->totalGears, dev->cookieSteps);
	}

	// 旋钮状态复位
	dev->taskIdWheel = NULL;
	dev->spinBeginTime = 0;
	dev->steps = 0;
}

_RAMCODE_ void wheel_spinIrqHandler(driver_wheel_t* dev, uint8_t isPinA) {
	// 未监听时，直接返回
	if(!dev || !dev->listen) { return; }
	
	// 判断是否是真的转动了(防止误触发中断)
	uint8_t spinState = dev->funcPinBIsOn();
	if(spinState == dev->spinState) { return; }
	dev->spinState = spinState;

	// 当A脚触发中断时，A/B 电平相同表示逆时针旋转，否则是顺时针
	// 当B脚触发中断时，A/B 电平不相同表示逆时针旋转，否则是顺时针
    s8 dir = ((isPinA && (dev->funcPinAIsOn() == spinState)) || (!isPinA && (dev->funcPinAIsOn() != spinState))) ? -1 : 1;
	dev->steps += dir;

	// 在中断中调用单步回调函数？！
	if(dev->onStep) { dev->onStep(dir, dev->cookieStep); }

	if(dev->taskIdWheel) {
		// 检查旋钮转动时，按钮是否处于按下状态
		// 在旋转过程中，只要有一次检查到按钮处于按下状态，则认为此次旋转为 DOWN_FORWARD / DOWN_BACKWARD
		dev->downOnSpin |= dev->funcIsDown ? dev->funcIsDown() : false;
		// 旋转继续，任务重新计时
		osTaskTimerReset(dev->taskIdWheel);
	} else {
		// 如果刚开始旋转，则记下开始旋转的时间点
		dev->spinBeginTime = osGetMs();
		// 初始化按钮按下标记
		dev->downOnSpin = dev->funcIsDown ? dev->funcIsDown() : false;
		// 创建结束旋转延时任务
		dev->taskIdWheel = osTaskRunAfter(WHEEL_LAZY_INTERVAL, checkSpinStateTask, sizeof(void*), &dev);
	}
}


/// @}
